// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.android.cloudapp;

import static com.android.cloudapp.CloudAppMsg.HEARTBEAT_MSG_ID;
import static com.android.cloudapp.CloudAppMsg.MSG_HEADER_LEN;
import static com.android.cloudapp.CloudAppMsg.MSG_MAGICWORD;

import android.os.RemoteException;

public class CloudAppDataChannelClient {
    private static final String TAG = "CloudAppDataChannelClient";
    private final String mUnixIpcName = "CLOUD_APP_DATA_CHANNEL";
    private CloudAppUnixSocket mUnixSocket = null;
    private CloudAppDataChannelMsgListener mMsgListener = null;
    private CloudAppDataChannelDisconnectListener mDisconnectListener = null;
    private DataChannelRecvThread mRecvThread = null;
    private DataChannelHeartbeatThread mHeartbeatThread = null;
    private boolean isTaskThreadRun = false;

    CloudAppDataChannelClient() {
        mUnixSocket = new CloudAppUnixSocket(mUnixIpcName);
        mRecvThread = new DataChannelRecvThread();
        mHeartbeatThread = new DataChannelHeartbeatThread();
    }

    public synchronized int sendMsg(byte[] data, short msgId) {
        byte[] header = new byte[MSG_HEADER_LEN];
        header[0] = (byte) (msgId >> 8);
        header[1] = (byte) (msgId & 0x00FF);
        if (data == null) {
            header[2] = 00;
            header[3] = 00;
        } else {
            header[2] = (byte) ((data.length & 0xFF00) >> 8);
            header[3] = (byte) (data.length & 0x00FF);
        }
        header[4] = MSG_MAGICWORD >> 8;
        header[5] = MSG_MAGICWORD & 0x00FF;

        int ret = mUnixSocket.send(header);
        if (ret == -1) {
            CloudAppLog.e(TAG, "Unix socket send msg header error.");
            return -1;
        }
        if (data != null) {
            CloudAppLog.d(TAG, "Unix socket send msg id = " + msgId + " msgLen = " + data.length);
            ret = mUnixSocket.send(data);
            if (ret == -1) {
                CloudAppLog.e(TAG, "Unix socket send msg body error.");
                return -1;
            }
        }
        return 0;
    }

    private int sendHeartbeat() {
        return sendMsg(null, HEARTBEAT_MSG_ID);
    }

    public void setMsgListener(CloudAppDataChannelMsgListener listener) {
        mMsgListener = listener;
    }

    public void setDisconnectListener(CloudAppDataChannelDisconnectListener listener) {
        mDisconnectListener = listener;
    }

    public int connect() {
        return mUnixSocket.connect();
    }

    public void close() {
        mUnixSocket.close();
    }

    public int start() {
        isTaskThreadRun = true;
        try {
            mRecvThread.start();
            mHeartbeatThread.start();
            return 0;
        } catch (Exception e) {
            e.printStackTrace();
            return -1;
        }
    }

    public void stop() {
        isTaskThreadRun = false;
        try {
            mRecvThread.join(2000, 0);
            mHeartbeatThread.join(2000, 0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class DataChannelRecvThread extends Thread {
        @Override
        public void run() {
            CloudAppLog.i(TAG, "DataChannelRecvThread run.");
            byte[] data = new byte[MSG_HEADER_LEN];
            int bytes = 0;
            while (isTaskThreadRun) {
                bytes = mUnixSocket.recv(data, MSG_HEADER_LEN);
                if (bytes == -1) {
                    CloudAppLog.e(TAG, "Unix socket recv error.");
                    break;
                }
                CloudAppMsg.CloudAppMsgHeader header = new CloudAppMsg.CloudAppMsgHeader(data);
                if (header.getmMsgId() == HEARTBEAT_MSG_ID) {
                    CloudAppLog.d(TAG, "Recv heardbeat.");
                } else {
                    short bodyLen = header.getmMsgBodyLen();
                    CloudAppLog.d(TAG, "Recv msg id = " + header.getmMsgId()
                            + "and msg len = " + bodyLen);
                    byte[] msgBody = new byte[bodyLen];
                    bytes = mUnixSocket.recv(msgBody, bodyLen);
                    if (bytes == -1) {
                        CloudAppLog.e(TAG, "Unix socket recv error.");
                        break;
                    }
                    if (mMsgListener != null) {
                        try {
                            mMsgListener.onRecvMsg(msgBody);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            CloudAppLog.i(TAG, "DataChannelRecvThread exit.");
        }
    }

    class DataChannelHeartbeatThread extends Thread {
        @Override
        public void run() {
            CloudAppLog.i(TAG, "DataChannelHeartbeatThread run.");
            while (isTaskThreadRun) {
                if (sendHeartbeat() == -1) {
                    CloudAppLog.e(TAG, "Send heartbeat error.");
                    if (mDisconnectListener != null) {
                        mDisconnectListener.onDisconnect();
                    }
                    break;
                }
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            CloudAppLog.i(TAG, "DataChannelHeartbeatThread exit.");
        }
    }

    public interface CloudAppDataChannelDisconnectListener {
        void onDisconnect();
    }

    public interface CloudAppDataChannelMsgListener {
        void onRecvMsg(byte[] data) throws RemoteException;
    }
}
